'use strict';

layui.define(['jquery'], function(e) {
  // ie8 polyfill

  // ref: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/Trim
  // eslint-disable-next-line no-extend-native
  String.prototype.trim = String.prototype.trim || function() {
    return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
  };

  // eslint-disable-next-line no-extend-native
  String.prototype.trimAll = String.prototype.trimAll || function() {
    return this.replace(/\s+/g, '');
  };

  // eslint-disable-next-line no-extend-native
  String.prototype.capitalize = String.prototype.capitalize || function() {
    return this.substring(0, 1).toUpperCase() + this.substring(1);
  };

  // ref: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
  // eslint-disable-next-line no-extend-native
  Array.prototype.isArray = Array.prototype.isArray || function(input) {
    return Object.prototype.toString.call(input) === '[object Array]';
  };

  // ref: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
  // eslint-disable-next-line no-extend-native
  Array.prototype.indexOf = Array.prototype.indexOf || function(searchElement, fromIndex) {
    var k;

    // 1. Let O be the result of calling ToObject passing
    //    the this value as the argument.
    if (this == null) {
      throw new TypeError('"this" is null or not defined');
    }

    var O = Object(this);

    // 2. Let lenValue be the result of calling the Get
    //    internal method of O with the argument "length".
    // 3. Let len be ToUint32(lenValue).
    var len = O.length >>> 0;

    // 4. If len is 0, return -1.
    if (len === 0) {
      return -1;
    }

    // 5. If argument fromIndex was passed let n be
    //    ToInteger(fromIndex); else let n be 0.
    var n = +fromIndex || 0;

    if (Math.abs(n) === Infinity) {
      n = 0;
    }

    // 6. If n >= len, return -1.
    if (n >= len) {
      return -1;
    }

    // 7. If n >= 0, then Let k be n.
    // 8. Else, n<0, Let k be len - abs(n).
    //    If k is less than 0, then let k be 0.
    k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);

    // 9. Repeat, while k < len
    while (k < len) {
      // a. Let Pk be ToString(k).
      //   This is implicit for LHS operands of the in operator
      // b. Let kPresent be the result of calling the
      //    HasProperty internal method of O with argument Pk.
      //   This step can be combined with c
      // c. If kPresent is true, then
      //    i.  Let elementK be the result of calling the Get
      //        internal method of O with the argument ToString(k).
      //   ii.  Let same be the result of applying the
      //        Strict Equality Comparison Algorithm to
      //        searchElement and elementK.
      //  iii.  If same is true, return k.
      if (k in O && O[k] === searchElement) {
        return k;
      }
      k++;
    }
    return -1;
  };

  // eslint-disable-next-line no-extend-native
  Array.prototype.remove = Array.prototype.remove || function(input) {
    var index = this.indexOf(input);
    index > -1 && this.splice(index, 1);
  };

  // ref: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
  // eslint-disable-next-line no-extend-native
  Array.prototype.forEach = Array.prototype.forEach || function(callback, thisArg) {
    var T, k;

    if (this == null) {
      throw new TypeError(' this is null or not defined');
    }

    // 1. Let O be the result of calling toObject() passing the
    // |this| value as the argument.
    var O = Object(this);

    // 2. Let lenValue be the result of calling the Get() internal
    // method of O with the argument "length".
    // 3. Let len be toUint32(lenValue).
    var len = O.length >>> 0;

    // 4. If isCallable(callback) is false, throw a TypeError exception.
    // See: http://es5.github.com/#x9.11
    if (typeof callback !== 'function') {
      throw new TypeError(callback + ' is not a function');
    }

    // 5. If thisArg was supplied, let T be thisArg; else let
    // T be undefined.
    if (arguments.length > 1) {
      T = thisArg;
    }

    // 6. Let k be 0
    k = 0;

    // 7. Repeat, while k < len
    while (k < len) {
      var kValue;

      // a. Let Pk be ToString(k).
      //    This is implicit for LHS operands of the in operator
      // b. Let kPresent be the result of calling the HasProperty
      //    internal method of O with argument Pk.
      //    This step can be combined with c
      // c. If kPresent is true, then
      if (k in O) {
        // i. Let kValue be the result of calling the Get internal
        // method of O with argument Pk.
        kValue = O[k];

        // ii. Call the Call internal method of callback with T as
        // the this value and argument list containing kValue, k, and O.
        callback.call(T, kValue, k, O);
      }
      // d. Increase k by 1.
      k++;
    }
    // 8. return undefined
  };

  // ref: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map
  // eslint-disable-next-line no-extend-native
  Array.prototype.map = Array.prototype.map || function(callback/*, thisArg*/) {
    var T, A, k;

    if (this == null) {
      throw new TypeError('this is null or not defined');
    }

    // 1. Let O be the result of calling ToObject passing the |this|
    //    value as the argument.
    var O = Object(this);

    // 2. Let lenValue be the result of calling the Get internal
    //    method of O with the argument "length".
    // 3. Let len be ToUint32(lenValue).
    var len = O.length >>> 0;

    // 4. If IsCallable(callback) is false, throw a TypeError exception.
    // See: http://es5.github.com/#x9.11
    if (typeof callback !== 'function') {
      throw new TypeError(callback + ' is not a function');
    }

    // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
    if (arguments.length > 1) {
      T = arguments[1];
    }

    // 6. Let A be a new array created as if by the expression new Array(len)
    //    where Array is the standard built-in constructor with that name and
    //    len is the value of len.
    A = new Array(len);

    // 7. Let k be 0
    k = 0;

    // 8. Repeat, while k < len
    while (k < len) {
      var kValue, mappedValue;

      // a. Let Pk be ToString(k).
      //   This is implicit for LHS operands of the in operator
      // b. Let kPresent be the result of calling the HasProperty internal
      //    method of O with argument Pk.
      //   This step can be combined with c
      // c. If kPresent is true, then
      if (k in O) {
        // i. Let kValue be the result of calling the Get internal
        //    method of O with argument Pk.
        kValue = O[k];

        // ii. Let mappedValue be the result of calling the Call internal
        //     method of callback with T as the this value and argument
        //     list containing kValue, k, and O.
        mappedValue = callback.call(T, kValue, k, O);

        // iii. Call the DefineOwnProperty internal method of A with arguments
        // Pk, Property Descriptor
        // { Value: mappedValue,
        //   Writable: true,
        //   Enumerable: true,
        //   Configurable: true },
        // and false.

        // In browsers that support Object.defineProperty, use the following:
        // Object.defineProperty(A, k, {
        //   value: mappedValue,
        //   writable: true,
        //   enumerable: true,
        //   configurable: true
        // });

        // For best browser support, use the following:
        A[k] = mappedValue;
      }
      // d. Increase k by 1.
      k++;
    }

    // 9. return A
    return A;
  };

  // ref: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
  // eslint-disable-next-line no-extend-native
  Array.prototype.filter = Array.prototype.filter || function(func, thisArg) {
    'use strict';
    // eslint-disable-next-line valid-typeof
    if (!((typeof func === 'Function' || typeof func === 'function') && this)) { throw new TypeError() }

    var len = this.length >>> 0;
    var res = new Array(len); // preallocate array
    var t = this; var c = 0; var i = -1;
    if (thisArg === undefined) {
      while (++i !== len) {
        // checks to see if the key was set
        if (i in this) {
          if (func(t[i], i, t)) {
            res[c++] = t[i];
          }
        }
      }
    } else {
      while (++i !== len) {
        // checks to see if the key was set
        if (i in this) {
          if (func.call(thisArg, t[i], i, t)) {
            res[c++] = t[i];
          }
        }
      }
    }

    res.length = c; // shrink down array to proper size
    return res;
  };

  var $ = layui.jquery;
  var MODULE_NAME = 'helper';
  var $body = $('body');
  var util = {
    isObject: function(input) {
      return Object.prototype.toString.call(input) === '[object Object]';
    },
    isArray: function(input) {
      return Array.isArray ? Array.isArray(input) : $.isArray(input);
    },
    isDate: function(input) {
      return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
    },
    isNumber: function(input) {
      return input instanceof Number || Object.prototype.toString.call(input) === '[object Number]';
    },
    isString: function(input) {
      return input instanceof String || Object.prototype.toString.call(input) === '[object String]';
    },
    isBoolean: function(input) {
      return typeof input === 'boolean';
    },
    isFunction: function(input) {
      return typeof input === 'function';
    },
    isNull: function(input) {
      return input === undefined || input === null;
    },
    isDef: function(input) {
      return this.isNull(input);
    },
    isUndef: function(input) {
      return !this.isDef(input);
    },
    uuid: function() {
      return Math.random().toString(36).slice(4);
    },
    // 复制对象
    copy: function(data) {
      var copyOne = null;
      if (this.isObject(data)) {
        copyOne = {};
        for (var key in data) {
          copyOne[key] = this.copy(data[key]);
        }
      } else if (this.isArray(data)) {
        copyOne = [];
        data.forEach(function(index) {
          copyOne.push(this.copy(index));
        });
      } else {
        copyOne = data;
      }
      return copyOne;
    },
    // 深度获取对象值 util.getKeyValue({id:{v:'a'},b:2},"id.v") => a
    getKeyValue: function(obj, keypath) {
      if (!this.isObject(obj)) {
        return null;
      }
      var array = null;
      if (this.isArray(keypath)) {
        array = keypath;
      } else if (this.isString(keypath)) {
        array = keypath.split('.');
      }
      if (array == null || array.length === 0) {
        return null;
      }
      var value = null;
      var key = array.shift();
      var keyTest = key.match(new RegExp('^(\\w+)\\[(\\d+)\\]$'));
      if (keyTest) {
        key = keyTest[1];
        var index = keyTest[2];
        value = obj[key];
        if (this.isArray(value) && value.length > index) {
          value = value[index];
        }
      } else {
        value = obj[key];
      }

      if (array.length > 0) {
        return this.getKeyValue(value, array);
      }
      return value;
    },
    // 对象深度赋值 utils.setKeyValue({id:{v:['a','b']},b:2}, "id.v[1]", 'c') => {id:{v:['a','c']},b:2}
    setKeyValue: function(obj, keypath, value, orignal) {
      if (!this.isObject(obj)) {
        return false;
      }
      var array = null;
      if (this.isArray(keypath)) {
        array = keypath;
      } else if (this.isString(keypath)) {
        array = keypath.split('.');
        orignal = obj;
      }
      if (array == null || array.length === 0) {
        return false;
      }
      var children = null;
      var index = 0;
      var key = array.shift();
      var keyTest = key.match(new RegExp('^(\\w+)\\[(\\d+)\\]$'));
      if (keyTest) {
        key = keyTest[1];
        index = keyTest[2];
        children = obj[key];
        if (this.isArray(children) && children.length > index) {
          if (array.length > 0) {
            return this.setKeyValue(children[index], array, value, orignal);
          }
          children[index] = value;
        }
      } else {
        if (array.length > 0) {
          return this.setKeyValue(obj[key], array, value, orignal);
        }
        obj[key] = value;
      }
      return orignal;
    },
    add: function(arg1, arg2) {
      var s1 = arg1.toString();
      var s2 = arg2.toString();
      var arg1Arr = s1.split('.');
      var arg2Arr = s2.split('.');
      var d1 = arg1Arr.length === 2 ? arg1Arr[1] : '';
      var d2 = arg2Arr.length === 2 ? arg2Arr[1] : '';
      var maxLen = Math.max(d1.length, d2.length);
      var m = Math.pow(10, maxLen);
      return Number(((s1 * m + s2 * m) / m).toFixed(maxLen));
    },
    sub: function(arg1, arg2) {
      return this.add(arg1, -arg2);
    },
    mul: function(arg1, arg2) {
      var m = 0;
      var s1 = arg1.toString();
      var s2 = arg2.toString();
      try {
        m += s1.split('.')[1].length;
      } catch (e) {
        //
      }
      try {
        m += s2.split('.')[1].length;
      } catch (e) {
        //
      }
      return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m);
    },
    div: function(arg1, arg2) {
      var t1 = 0;
      var t2 = 0;
      try {
        t1 = arg1.toString().split('.')[1].length;
      } catch (e) {
        //
      }
      try {
        t2 = arg2.toString().split('.')[1].length;
      } catch (e) {
        //
      }
      var r1 = Number(arg1.toString().replace('.', ''));
      var r2 = Number(arg2.toString().replace('.', ''));
      return r1 / r2 * Math.pow(10, t2 - t1);
    },
    trim: function(string) {
      return (string || '').trim();
    },
    trimAll: function(string) {
      return (string || '').trimAll();
    },
    cut: function(string, len) {
      if (string.length > len) {
        return string.substring(0, len) + '...';
      } else {
        return string;
      }
    },
    beginWith: function(string, tag) {
      return string.substring(0, tag.length) === tag;
    },
    endWith: function(string, tag) {
      return string.substring(string.length - tag.length) === tag;
    },
    // 字节长度
    byteSize: function(string, boolean) {
      return this.isString(this.isNumber(string) ? (string + '') : string) ? boolean ? string.replace(/[^\x00-\xff]/g, '__').length : string.length : 0;
    },
    // 文本节点长度
    textNodeSize: function(string) {
      var $outer = $('<div />');
      $outer.css({
        position: 'fixed',
        top     : -9999,
        left    : -9999,
        zIndex  : -9999,
        opacity : 0
      }).text(string);
      $body.append($outer);
      var width = $outer.width();
      $outer.remove();
      return width;
    },
    getURLParam: function(name, search) {
      return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(search || location.search) || [true, ''])[1].replace(/\+/g, '%20')) || null;
    },
    parseURLParam: function(url, option) {
      url = this.isString(url) ? url : '';
      url = this.endWith(url, '?') ? url : url.concat('?');

      if ($.isPlainObject(option)) {
        $.each(option, function(key, value) {
          url += key + '=' + (/^[\u4e00-\u9fa5_a-zA-Z0-9]+$/.test(value) ? encodeURIComponent(value) : value) + '&';
        });
        url = url.substring(0, url.length - 1);
      }

      return url;
    },
    // 获取完整路径
    resolvePath: function() {
      var _this = this;
      var args = $.makeArray(arguments);
      var merge = function(basePath, path) {
        if (!basePath) return path;

        if (_this.endWith(basePath, '/')) {
          basePath.slice(0, basePath.length - 1);
        }

        if (!_this.beginWith(path, '/')) {
          path = '/' + path;
        }

        return basePath + path;
      };
      var resolve = function(basePath, path) {
        return /^http(s)?:\/\/.+/.test(path) || path.indexOf(basePath) > -1 ? path : merge(basePath, path);
      };
      var ret = '';
      var isIgnore = _this.isBoolean(args[0]) && args[0];

      isIgnore && args.shift();

      $.each(args, function(index, item) {
        ret = isIgnore ? merge(ret, item) : resolve(ret, item);
      });
      return ret;
    },
    styleInject: function(css, ref) {
      if (ref === void 0) ref = {};
      var insertAt = ref.insertAt;
      var insertId = ref.id;

      if (!css || typeof document === 'undefined') {
        return;
      }

      var head = document.head || document.getElementsByTagName('head')[0];
      var style = document.createElement('style');
      style.type = 'text/css';
      if (insertId) {
        style.id = insertId;
      }

      if (insertAt === 'top') {
        if (head.firstChild) {
          head.insertBefore(style, head.firstChild);
        } else {
          head.appendChild(style);
        }
      } else {
        head.appendChild(style);
      }

      if (style.styleSheet) {
        style.styleSheet.cssText = css;
      } else {
        style.appendChild(document.createTextNode(css));
      }
    }
  };

  e(MODULE_NAME, util);
});
